CloudWatch GetMetricData でリクエストされているメトリクス内訳を CloudTrailログから把握する
とある環境にて CloudWatchメトリクス取得の料金( GetMetricData )を最適化しようとしています。 以下のように継続的にコストになっています。
この環境では Datadog のAWS統合(インテグレーション) を導入しています。 AWS統合により CloudWatchメトリクスが収集されるのですが、これがコストとなっています。
このコストを抑えたいのですが、少し情報が足りません。 実際に「 どの名前空間(やメトリクス) にリクエストがされているのか 」、内訳を知りたいです。
悩んでいたところ以下ブログにて GetMetricData ログを取得できるようになったと知りました。
「これを使って調査しよう」、と思い立って本ブログを書きました。
とりあえずGetMetricDataログを取ってみる
CloudTrailデータイベントを有効化して GetMetricDataログを取得するようにします。 (手順は いわささんのアップデートブログ に丁寧に記載されているので割愛します)
CloudWatch Logs に数日分の GetMetricDataログを出力します。 以下のように Datadogからのログを確認できました。
こんな感じ(↓)のログが大量に出力されます。
GetMetricDataログのサンプル
{
"eventVersion": "1.10",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAEXAMPLE:DatadogAWSIntegration",
"arn": "arn:aws:sts::123456789012:assumed-role/DatadogIntegrationRole/DatadogAWSIntegration",
"accountId": "123456789012",
"accessKeyId": "ASIAEXAMPLE",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROAEXAMPLE",
"arn": "arn:aws:iam::123456789012:role/DatadogIntegrationRole",
"accountId": "123456789012",
"userName": "DatadogIntegrationRole"
},
"attributes": {
"creationDate": "2024-08-18T19:14:00Z",
"mfaAuthenticated": "false"
}
}
},
"eventTime": "2024-08-18T19:48:13Z",
"eventSource": "monitoring.amazonaws.com",
"eventName": "GetMetricData",
"awsRegion": "us-east-1",
"sourceIPAddress": "X.X.X.X",
"userAgent": "python-urllib3/X.X.X",
"requestParameters": {
"metricDataQueries": [
{
"id": "m872",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Resource",
"value": "RefreshTrustedAdvisorCheck"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "Support"
},
{
"name": "Class",
"value": "None"
}
]
},
"period": 60,
"stat": "Maximum"
}
},
{
"id": "m873",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Resource",
"value": "RefreshTrustedAdvisorCheck"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "Support"
},
{
"name": "Class",
"value": "None"
}
]
},
"period": 60,
"stat": "Minimum"
}
},
{
"id": "m874",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Resource",
"value": "RefreshTrustedAdvisorCheck"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "Support"
},
{
"name": "Class",
"value": "None"
}
]
},
"period": 60,
"stat": "Sum"
}
},
{
"id": "m875",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Class",
"value": "None"
},
{
"name": "Resource",
"value": "SelectResourceConfig"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "AWS Config"
}
]
},
"period": 60,
"stat": "Average"
}
},
{
"id": "m876",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Class",
"value": "None"
},
{
"name": "Resource",
"value": "SelectResourceConfig"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "AWS Config"
}
]
},
"period": 60,
"stat": "Maximum"
}
},
{
"id": "m877",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Class",
"value": "None"
},
{
"name": "Resource",
"value": "SelectResourceConfig"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "AWS Config"
}
]
},
"period": 60,
"stat": "Minimum"
}
},
{
"id": "m878",
"metricStat": {
"metric": {
"namespace": "AWS/Usage",
"metricName": "CallCount",
"dimensions": [
{
"name": "Class",
"value": "None"
},
{
"name": "Resource",
"value": "SelectResourceConfig"
},
{
"name": "Type",
"value": "API"
},
{
"name": "Service",
"value": "AWS Config"
}
]
},
"period": 60,
"stat": "Sum"
}
}
],
"startTime": "Aug 18, 2024, 7:28:10 PM",
"endTime": "Aug 18, 2024, 7:48:13 PM"
},
"responseElements": null,
"requestID": "5160example-xxxx",
"eventID": "8a44example-xxxx",
"readOnly": true,
"resources": [
{
"type": "AWS::CloudWatch::Metric"
}
],
"eventType": "AwsApiCall",
"managementEvent": false,
"recipientAccountId": "123456789012",
"eventCategory": "Data",
"tlsDetails": {
"tlsVersion": "TLSv1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"clientProvidedHostHeader": "monitoring.us-east-1.amazonaws.com"
}
}
GetMetricDataログを加工、集計してみる
GetMetricData の料金は、「このAPIが叩かれた回数」 ではなく 「リクエストされたメトリクス数」です。
GetMetricData、GetInsightRuleReport USD 0.01/リクエストされた 1,000 個のメトリクス
なので、このログから「どんなメトリクスがどれだけリクエストされているか」を見る必要があります。
CloudWatch Logs Insight でログをダウンロード
CloudWatch Logs Insight にて、1日分の GetMetricDataログを取得します。
1日分の範囲を指定して、以下クエリを実行します。
fields @timestamp, @message
| filter (
userIdentity.sessionContext.sessionIssuer.userName = "DatadogIntegrationRole"
and eventName = "GetMetricData" )
| sort @timestamp desc
| limit 10000
[テーブルをダウンロード(JSON)] を選択して、JSONデータをローカルに保存します。
以下のようなJSONです。
[
{
"@timestamp": "2024-08-16 14:53:16.675",
"@message": {
"eventVersion": "1.09",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAEXAMPLE:DatadogAWSIntegration",
"arn": "arn:aws:sts::123456789012:assumed-role/DatadogIntegrationRole/DatadogAWSIntegration",
"accountId": "123456789012",
# ... 略
jq で集計
得られたログを jq コマンド (ほか) を使って集計します。
jq クエリで「リクエストされた名前空間(namespace)」を列挙してみます。
jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|.namespace' ./logs-insights-results.json \
| head
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
# AWS/Config
これを sort および uniq コマンドで集計します。
jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|.namespace' ./logs-insights-results.json \
| sort | uniq -c | sort -nr
# 48730 AWS/TrustedAdvisor
# 22249 AWS/Usage
# 8707 AWS/Config
# 5464 AWS/States
# 2880 AWS/DynamoDB
# 447 AWS/Events
# 370 AWS/Logs
# 342 AWS/Firehose
# 244 AWS/Location
# 164 AWS/SNS
# 122 AWS/SecretsManager
# 81 AWS/S3
# 48 AWS/HealthLake
意外にも Trusted Advisor 関連のメトリクスのリクエストが多かったです。 グラフにするとこんな感じ。
また、「名前空間 > メトリクス名」でも集計してみます。
jq -r '.[]."@message".requestParameters.metricDataQueries[]?.metricStat.metric|[.namespace, .metricName]|@tsv' ./logs-insights-results.json \
| sort | uniq -c | sort -nr
# 43984 AWS/TrustedAdvisor ServiceLimitUsage
# 18413 AWS/Usage CallCount
# 8361 AWS/Config ConfigurationRecorderInsufficientPermissionsFailure
# 3377 AWS/Usage ResourceCount
# 2377 AWS/TrustedAdvisor YellowResources
# 2369 AWS/TrustedAdvisor RedResources
# 1366 AWS/States ThrottledEvents
# 1366 AWS/States ProvisionedRefillRate
# 1366 AWS/States ProvisionedBucketSize
# 1366 AWS/States ConsumedCapacity
# 432 AWS/DynamoDB MaxProvisionedTableWriteCapacityUtilization
# 432 AWS/DynamoDB MaxProvisionedTableReadCapacityUtilization
# 432 AWS/DynamoDB AccountProvisionedWriteCapacityUtilization
# 432 AWS/DynamoDB AccountProvisionedReadCapacityUtilization
# 274 AWS/Config ConfigurationItemsRecorded
# ...略
こちらもグラフにしてみました。
注意点: リクエストパラメータが省略されることがある
リクエストパラメータのサイズが大きいと、以下のようにログの中身が省略されます。
"requestParameters": {
"omitted": true,
"originalSize": 133746,
"reason": "requestParameters too large"
},
この件はCloudTrailのユーザーガイドにも記載があります。
requestParameters
リクエストとともに送信されたパラメータ (ある場合)。 これらのパラメータは、適切な AWS サービスのAPIリファレンスドキュメントに記載されています。 このフィールドの最大サイズは 100 KB です。この制限を超えるコンテンツは切り捨てられます。
今回だと 1761件のうち 361件が省略されていました。 厳密な分析ができないのは少しモヤモヤしますが、これに関しては仕方ないですね。
おわりに
以上、GetMetricDataログからメトリクス内訳を確認しました。
ちなみに、このあと Datadog から AWS/TrustedAdvisor
メトリクスを収集しないように設定したところ、 見積もりどおり GetMetricData コストが削減されました。
リクエストパラメータの一部省略は少しかゆいところですが、 「どの名前空間のメトリクスがリクエストされているか」をざっくり把握したいときは役に立ちそうです。
以上、参考になれば幸いです。
参考